

// Copyright (C) 2013-2014 Altera Corporation, San Jose, California, USA. All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to
// whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// This agreement shall be governed in all respects by the laws of the State of California and
// by the laws of the United States of America.

///////////////////////////////////////////////////////////////////////////////////
// This host program executes a vector addition kernel to perform:
//  C = A + B
// where A, B and C are vectors with N elements.
//
// This host program supports partitioning the problem across multiple OpenCL
// devices if available. If there are M available devices, the problem is
// divided so that each device operates on N/M points. The host program
// assumes that all devices are of the same type (that is, the same binary can
// be used), but the code can be generalized to support different device types
// easily.
//
// Verification is performed against the same computation on the host CPU.
///////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
//#include <io.h>
#include <iostream>
#include "CL/opencl.h"
#include "AOCLUtils/aocl_utils.h"
#include "rsa.h"

using namespace aocl_utils;

// OpenCL runtime configuration
cl_platform_id platform = NULL;
unsigned num_devices = 1;
scoped_array<cl_device_id> device; // num_devices elements
cl_context context = NULL;
scoped_array<cl_command_queue> queue; // num_devices elements
cl_program program = NULL;
scoped_array<cl_kernel> kernel; // num_devices elements
scoped_array<cl_mem> input_a_buf; // num_devices elements
scoped_array<cl_mem> input_b_buf; // num_devices elements
scoped_array<cl_mem> input_n_buf; // num_devices elements
scoped_array<cl_mem> input_R_buf; // num_devices elements
scoped_array<cl_mem> output_buf; // num_devices elements

// Problem data.
//unsigned N = 1000000; // problem size
int input_bw = 64;//1024;
int cal_bw = 31;
int wordNum = (2048/cal_bw+1);//64;//32;
int n_num = 2048;//17;//1023;//15;//2048；
int eBitNum = 2048-1;//17-1;//(11-1);//(5-1);//(1023-1);//(253-1);//2040-1;//
int ew = (2048/cal_bw+3)*cal_bw;//2052+54;//2244;//2052+54;//1024+64;
//int big = 3;
ulong mask = ((1<<cal_bw) - 1);
uint *RR,*NN;
ulong R64[32];
ulong mod0,temp1,temp2,temp3;
scoped_array<scoped_aligned_ptr<ulong> > input_a, input_b, input_n, input_nr, input_R, input_p, input_q, input_d; // num_devices elements
scoped_array<scoped_aligned_ptr<ulong> > input_e32,input_n32,input_d32,input_c32,input_m32;
scoped_array<scoped_array<ulong> > ref_output; // num_devices elements
//scoped_array<unsigned> n_per_device; // num_devices elements

FILE *fp_test;

// Function prototypes
void init_problem();
void run();
void cleanup();

// Entry point.
int main(int argc, char **argv) {
  Options options(argc, argv);

  fp_test = fopen("test.dat","w");
    
  // Initialize the problem data.
  // Requires the number of devices to be known.
  init_problem();
  printf("Initial Problem ok!\n");

  // Run the kernel.
  run();
  cleanup();

  return 0;
}

// Initialize the data for the problem. Requires num_devices to be known.
void init_problem() {

  input_a.reset(num_devices);  
  input_b.reset(num_devices);
  input_R.reset(num_devices);
  input_n.reset(num_devices);
  input_nr.reset(num_devices);
  input_p.reset(num_devices);
  input_q.reset(num_devices);
  input_d.reset(num_devices);
  input_m32.reset(num_devices);
  input_e32.reset(num_devices);
  input_d32.reset(num_devices);
  input_n32.reset(num_devices);
  input_c32.reset(num_devices);
  ref_output.reset(num_devices);

  for(unsigned i = 0; i < num_devices; ++i) {
    input_a[i].reset(wordNum);    
    input_b[i].reset(wordNum);
    input_R[i].reset(wordNum);
    input_n[i].reset(wordNum);
    input_nr[i].reset(wordNum);
    input_p[i].reset(wordNum);
    input_q[i].reset(wordNum);
    input_d[i].reset(wordNum);
    input_m32[i].reset(wordNum);
    input_e32[i].reset(wordNum);
    input_n32[i].reset(wordNum);
    input_c32[i].reset(wordNum);
    input_d32[i].reset(wordNum);
    ref_output[i].reset(wordNum);

        //initial
    for(int j = 0; j < 64; ++j) {//
        //input_c32[i][j] = c[j];
        if(input_bw==64){
            input_m32[i][j] = (m[j/2]>>(32*(j%2)))&0xffffffff;
            input_d32[i][j] = (d[j/2]>>(32*(j%2)))&0xffffffff;
            input_n32[i][j] = (n[j/2]>>(32*(j%2)))&0xffffffff;
            input_c32[i][j] = (c[j/2]>>(32*(j%2)))&0xffffffff;
            input_e32[i][j] = (e[j/2]>>(32*(j%2)))&0xffffffff;
        //ref_output[i][j] = (m[j/2]>>(32*(j%2)))&0xffffffff;
      }
      else{
        input_m32[i][j] = m[j];
            input_d32[i][j] = d[j];
            input_n32[i][j] = n[j];
            input_c32[i][j] = c[j];
            input_e32[i][j] = e[j];
        //ref_output[i][j] = m[j];
      }//end if
    }//end for
      
     //modify for calculation
        for(int j = 0; j < (2048/cal_bw+1); ++j) {
      if(((j*cal_bw)/64)==(((j+1)*cal_bw)/64)){
        input_b[i][j] = (d[((j*cal_bw)/64)]>>((j*cal_bw)%64))&mask;
        ref_output[i][j] = (c[((j*cal_bw)/64)]>>((j*cal_bw)%64))&mask;
        input_n[i][j] = (n[((j*cal_bw)/64)]>>((j*cal_bw)%64))&mask;
        input_a[i][j] = (m[((j*cal_bw)/64)]>>((j*cal_bw)%64))&mask;
      }
      else{
        input_b[i][j] =  ((d[((j*cal_bw)/64)]>>((j*cal_bw)%64)) + (d[(((j+1)*cal_bw)/64)]<<(64-((j*cal_bw)%64))))&mask;
        ref_output[i][j]=((c[((j*cal_bw)/64)]>>((j*cal_bw)%64)) + (c[(((j+1)*cal_bw)/64)]<<(64-((j*cal_bw)%64))))&mask;
        input_n[i][j] =  ((n[((j*cal_bw)/64)]>>((j*cal_bw)%64)) + (n[(((j+1)*cal_bw)/64)]<<(64-((j*cal_bw)%64))))&mask;
        input_a[i][j] =  ((m[((j*cal_bw)/64)]>>((j*cal_bw)%64)) + (m[(((j+1)*cal_bw)/64)]<<(64-((j*cal_bw)%64))))&mask;
      }
      //if(j==(wordNum-1))//
      //    ref_output[i][j] = ((c[63]>>9)&0x7fffff);   
      //else if(((j*27)/32)==(((j+1)*27)/32))
      //    ref_output[i][j] = (c[((j*27)/32)]>>((j*27)%32))&0x7ffffff;
      //else
      //    ref_output[i][j] = ((c[((j*27)/32)]>>((j*27)%32)) + (c[(((j+1)*27)/32)]<<(32-((j*27)%32))))&0x7ffffff;
    }//end for j
  }//end for i
  
  //printf for mif or .h
  fprintf(fp_test,"\n print data for .mif or .h file\n");   
  fprintf(fp_test,"\n m: 32bit H->L\n");    
  for(int j=63; j>=0; j--){//shift  
        fprintf(fp_test,"  %x",input_m32[0][j]);
        if((j%8)==0)    fprintf(fp_test,"\n");      
  }//shift       
  fprintf(fp_test,"\n");
  fprintf(fp_test,"\n d: 32bit H->L\n");    
  for(int j=63; j>=0; j--){//shift  
        fprintf(fp_test,"  %x",input_d32[0][j]);
        if((j%8)==0)    fprintf(fp_test,"\n");      
  }//shift       
  fprintf(fp_test,"\n");
  fprintf(fp_test,"\n n: 32bit H->L\n");    
  for(int j=63; j>=0; j--){//shift  
        fprintf(fp_test,"  %x",input_n32[0][j]);
        if((j%8)==0)    fprintf(fp_test,"\n");      
  }//shift       
  fprintf(fp_test,"\n");
  fprintf(fp_test,"\n c: 32bit H->L\n");    
  for(int j=63; j>=0; j--){//shift  
        fprintf(fp_test,"  %x",input_c32[0][j]);
        if((j%8)==0)    fprintf(fp_test,"\n");      
  }//shift   
  fprintf(fp_test,"\n");
  fprintf(fp_test,"\n e: 32bit H->L\n");    
  for(int j=63; j>=0; j--){//shift  
        fprintf(fp_test,"  %x",input_e32[0][j]);
        if((j%8)==0)    fprintf(fp_test,"\n");      
  }//shift   
  fprintf(fp_test,"\n");
  
  //calculate R
  printf("calculate R start!\n");
  int RR_long = (2*ew/32)+1;
  RR = new uint [RR_long];
  NN = new uint [RR_long];
  RR[RR_long-1] = 1<<((2*ew)%32);
  for(unsigned i=0;i<(RR_long-1);i++)
    RR[i] = 0;
  fprintf(fp_test,"\n calculate nn: n_num:%d  ew:%d  RR_long:%d  RR:%x  mask:%x\n", n_num,ew,RR_long,RR[RR_long-1],mask);   
  for(unsigned i=0;i<RR_long;i++){//
    if(i>(SIZE-1))
        NN[i] = 0;
    else
        NN[i] = input_n32[0][i];
    //printf("  %x,  %x \n",NN[i],n[wordNum-i-1]);
    fprintf(fp_test,"  %x",NN[i]);
    if((i%8)==7)    fprintf(fp_test,"\n");  
  }//end for            
  fprintf(fp_test,"\n");
  calculateR(RR, (2*ew), NN, n_num, fp_test);  
  //printf the result
  fprintf(fp_test,"calculate R: 32bit for .h file\n");
  for(unsigned i=0;i<(RR_long-1);i++){
    fprintf(fp_test,"  %x",RR[i]);
    if((i%8)==7)    fprintf(fp_test,"\n");  
  }
  fprintf(fp_test,"\n");
  fprintf(fp_test,"calculate R: 32bit H->L for .mif file\n");
  for(int i=63; i>=0; i--){//shift  
        fprintf(fp_test,"  %x",RR[i]);
        if((i%8)==0)    fprintf(fp_test,"\n");  
  }
  fprintf(fp_test,"\n");
  //transf R32 to R64
  for(unsigned k=0; k<32; k++){//
    temp1 = (ulong)RR[k*2];
    temp2 = (ulong)RR[k*2+1];
    R64[k] = (temp2<<32) + (temp1&0xffffffff);
  }//
  for(int j=0; j<wordNum; j++){//
    if(((j*cal_bw)/64)==(((j+1)*cal_bw)/64))
        input_R[0][j] = (R64[((j*cal_bw)/64)]>>((j*cal_bw)%64))&mask;
    else
        input_R[0][j] = ((R64[((j*cal_bw)/64)]>>((j*cal_bw)%64)) + (R64[(((j+1)*cal_bw)/64)]<<(64-((j*cal_bw)%64))))&mask;
  }//          
  fprintf(fp_test,"\n");
  printf("calculate R end!\n");
}

void run() {
  int j,k;

  const double start_time = getCurrentTimestamp();

    //mod0
    printf("calculate mod0 start!\n");
    ulong mm = 1<<cal_bw;//(1<<32);
    mod0 = eculid(input_n[0][0],mm)&mask;
    mod0 = (mm - mod0)&mask;
    //mod0 = eculid(7,256); 
    fprintf(fp_test,"calculate mod0:  n0:%x   mod0:%x ok!\n", input_n[0][0], mod0);
    //InfInt myint1 = "4611686018427387904";//0x4000_0000_0000_0000;
    //myint1 = myint1*64;
    //printf("calculate mod0 n0:%llx  n1:%llx\n",n[0],n[1]);
    //InfInt myint2 = "4611686018427387904";
    //myint2 = myint2*4;
    //fprintf(fp_test,"myint1:%s   myint2:%s ok!\n", myint1.toString().c_str(), myint2.toString().c_str());
    //myint2 = myint2*(n[1]&0xffff);    
    //myint2 = myint2 + n[0];
    //fprintf(fp_test,"myint1:%s   myint2:%s ok!\n", myint1.toString().c_str(), myint2.toString().c_str());
    //InfInt mymod = eculid(myint2,myint1);
    //mymod = myint1 - mymod;
    //fprintf(fp_test,"calculate  mod0:%s   ok!\n", mymod.toString().c_str());
    //cout << mymod << endl;
        printf("calculate mod0 end!\n");

    // Enqueue kernel.
    uint Ei;
    int ii;
    unsigned long sumT[wordNum+2],sum[wordNum+2],b[wordNum+2],ar[wordNum+2];
    unsigned long carry,Q,ss;
    //unsigned long temp1,temp2,temp,temp3;
    //InfInt sumT[wordNum+2],sum[wordNum+2],b[wordNum+2],ar[wordNum+2];
    //InfInt carry,Q,ss;
    //InfInt temp1,temp2,temp,temp3;
    carry = 0;
    for (j=0; j<(wordNum+2); j++){//
        sumT[j] = 0;
        sum[j] = 0;
        b[j] = 0;
        ar[j] = 0;
    }
    
    //for test
    fprintf(fp_test,"\n test: R for cal\n");    
    for (j=0; j<wordNum; j++){//
        fprintf(fp_test,"  %x",input_R[0][j]);
        if((j%8)==7)    fprintf(fp_test,"\n");  
    }
    fprintf(fp_test,"\n test: c for .h file\n");    
    for (j=0; j<wordNum; j++){//
        fprintf(fp_test,"  %x",input_a[0][j]);
        if((j%8)==7)    fprintf(fp_test,"\n");  
    }
    fprintf(fp_test,"\n test: n for .h file\n");    
    for (j=0; j<wordNum; j++){//
        fprintf(fp_test,"  %x",input_n[0][j]);
        if((j%8)==7)    fprintf(fp_test,"\n");  
    }
    fprintf(fp_test,"\n");  
        
        //calculate the rsa
    for (ii=((eBitNum*2)+1); ii>=0; ii--){//rsa
        if(ii>0)// && ii<((eBitNum*2)+1))              
            Ei = (input_b[0][(ii-1)/(cal_bw*2)]>>(((ii-1)/2)%cal_bw))&1;//
        if(ii==0){//MMM_64(sum, 1, M)
            for(k=0; k<wordNum; k++){//
                if(k==0)
                    b[k] = 1;
                else
                    b[k] = 0;
            }
        }//MMM_64(sum, 1, M)
        else if(ii==((eBitNum*2)+1)){//calculate ar
            for(k=0; k<wordNum; k++){//
                b[k] = input_R[0][k];
                sum[k] = input_a[0][k];
            }//end for
        }//MMM_64(sum, sum, M)
        else if((ii%2)==0){//sum = sum^2,MMM_64(sum, sum, M)
            for(k=0; k<wordNum; k++)//
                b[k] = sum[k];
        }//MMM_64(sum, sum, M)
        else if((ii%2)==1 && Ei==1 && ii<(eBitNum*2)){//sum = sum^2,MMM_64(sum, x', M)
            for(k=0; k<wordNum; k++)//
                b[k] = ar[k];
        }//MMM_64(sum, x', M)
        else
            continue;

        //MMM_64
        for(j=0; j<(wordNum+2); j++){//Montgomery
            Q = ((sumT[0]+b[0]*sum[j])*mod0)&mask;//sumT[0]  
            for(k=0; k<wordNum; k++){//single_int*mul_int
                temp1 = Q*input_n[0][k];
                temp2 = b[k]*sum[j];
                temp3 = sumT[k] + (temp1&mask) + (temp2&mask) + carry;
                carry = ((temp1>>cal_bw)&mask) + ((temp2>>cal_bw)&mask) + ((temp3>>cal_bw)&mask);
                sumT[k] = (temp3&mask);
            }//single_int*mul_int   
            sumT[wordNum] = sumT[wordNum] + carry;
            for(k=0; k<wordNum; k++){//shift
                sumT[k] = sumT[k+1];
            }//shift   
            sumT[wordNum] = 0;
            carry = 0;
        }//Montgomery
        
        //#pragma unroll
        for(k=0; k<wordNum; k++){//
            sum[k] = sumT[k];
            if(ii==((eBitNum*2)+1)) ar[k] = sumT[k];
            sumT[k] = 0;            
        }
                       
        //if(ii<0xff){//fprintf
        //  if(ii>0)
        //      fprintf(fp_test,"\n result: i:%x  ee:%x  ei:%x  addr:%ld\n",ii,input_b[0][(ii-1)/54],Ei,(ii-1)/54);
        //  else
        //      fprintf(fp_test,"\n result: i:%x  ee:%x  ei:%x  Q:%ld\n",ii,input_b[0][0],Ei,Q);
        //  for(k=0; k<wordNum; k++){//
        //          fprintf(fp_test,"  %lx",sum[k]);
        //          if((k%8)==7)    fprintf(fp_test,"\n");      
        //  }//   
        ////    fprintf(fp_test,"\n ii:%x   Ei:%x\n", ii, Ei);
        //  fprintf(fp_test,"\n");
        //}//fprintf
    }//rsa
    fprintf(fp_test,"\n rsa result:\n");
        for(k=0; k<wordNum; k++){//
                fprintf(fp_test,"  %lx",sum[k]);
                if((k%8)==7)    fprintf(fp_test,"\n");      
        }// 
    fprintf(fp_test,"\n");
        
    fprintf(fp_test,"\n rsa aim:\n");
        for(k=0; k<wordNum; k++){//
                fprintf(fp_test,"  %lx",ref_output[0][k]);
                if((k%8)==7)    fprintf(fp_test,"\n");      
        }// 
    fprintf(fp_test,"\n");

  const double end_time = getCurrentTimestamp();

  // Wall-clock time taken.
  printf("\nTime: %0.3f ms\n", (end_time - start_time) * 1e3);
}

void cleanup() {
}

